﻿//------------------------------------------------------------------------------
// <copyright file="Selection.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;
using System.Diagnostics.CodeAnalysis;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Diagrams;
using Microsoft.VisualStudio.Modeling.Shell;
using VsShell = Microsoft.VisualStudio.Shell.Interop;
using System.Collections.ObjectModel;

namespace ModelingAddIns
{
    /// <summary>
    /// General VS selection representation.
    /// </summary>
    //[CLSCompliant(false)]
    public class Selection
    {
        #region Private data, factory method

        private IServiceProvider m_serviceProvider;
        private VsShell.ISelectionContainer m_selectionContainer;
        private List<object> m_selectedObjects;

        [SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands")]
        public static T Create<T>(IServiceProvider serviceProvider, VsShell.ISelectionContainer selectionContainer)
            where T : Selection, new()
        {
            if (serviceProvider == null) throw new ArgumentNullException("serviceProvider");

            T selection = new T();
            selection.m_serviceProvider = serviceProvider;

            // If no selection container provided, initialize it with the current selection in VS
            if (selectionContainer == null)
            {
                VsShell.IVsMonitorSelection monitorService = serviceProvider.GetService(typeof(VsShell.IVsMonitorSelection)) as VsShell.IVsMonitorSelection;
                Debug.Assert(monitorService != null);
                if (monitorService != null)
                {
                    IntPtr ppHier, ppSC;
                    uint pitemid;
                    VsShell.IVsMultiItemSelect ppMIS;
                    ErrorHandler.ThrowOnFailure(monitorService.GetCurrentSelection(out ppHier, out pitemid, out ppMIS, out ppSC));

                    if (ppSC != IntPtr.Zero)
                        selectionContainer = Marshal.GetObjectForIUnknown(ppSC) as VsShell.ISelectionContainer;
                }
            }
            selection.m_selectionContainer = selectionContainer;

            return selection;
        }

        #endregion

        #region Selection properties

        public IServiceProvider ServiceProvider
        {
            [DebuggerStepThrough]
            get { return m_serviceProvider; }
        }

        public VsShell.ISelectionContainer SelectionContainer
        {
            [DebuggerStepThrough]
            get { return m_selectionContainer; }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public IList<object> SelectedObjects
        {
            [DebuggerStepThrough]
            get
            {
                if (m_selectedObjects == null)
                {
                    m_selectedObjects = new List<object>();
                    if (m_selectionContainer != null)
                    {
                        uint count;
                        m_selectionContainer.CountObjects(2 /*selected*/, out count);
                        if (count > 0)
                        {
                            object[] result = new object[count];
                            m_selectionContainer.GetObjects(2 /*selected*/, count, result);
                            m_selectedObjects.AddRange(result);
                        }
                    }
                }
                return m_selectedObjects;
            }
        }

        #endregion

        #region Filtering methods

        public int Count<T>() where T : class
        {
            return Count<T>(null);
        }

        public int Count<T>(Predicate<T> predicate) where T : class
        {
            int result = 0;
            for (int i = 0; i < this.SelectedObjects.Count; i++)
            {
                T obj = this.SelectedObjects[i] as T;
                if (obj != null && (predicate == null || predicate(obj)))
                    result++;
            }
            return result;
        }

        public T FindFirst<T>() where T : class
        {
            return FindFirst<T>(null);
        }

        public T FindFirst<T>(Predicate<T> predicate) where T : class
        {
            for (int i = 0; i < this.SelectedObjects.Count; i++)
            {
                T obj = this.SelectedObjects[i] as T;
                if (obj != null && (predicate == null || predicate(obj)))
                    return obj;
            }
            return null;
        }

        public bool Exists<T>() where T : class
        {
            return FindFirst<T>(null) != null;
        }

        public bool Exists<T>(Predicate<T> predicate) where T : class
        {
            return FindFirst<T>(predicate) != null;
        }

        public IEnumerable<T> Filter<T>() where T : class
        {
            return Filter<T>(null);
        }

        public IEnumerable<T> Filter<T>(Predicate<T> predicate) where T : class
        {
            for (int i = 0; i < this.SelectedObjects.Count; i++)
            {
                T obj = this.SelectedObjects[i] as T;
                if (obj != null && (predicate == null || predicate(obj)))
                {
                    yield return obj;
                }
            }
        }

        #endregion
    }

    /// <summary>
    /// Diagram selection representation.
    /// </summary>
    //[CLSCompliant(false)]
    public class DiagramSelection : Selection
    {
        #region Private data

        private DiagramDocView m_docView;
        private ModelingDocData m_docData;
        private VSDiagramView m_diagramView;
        private DiagramClientView m_diagramClientView;
        private Diagram m_diagram;
        private Store m_store;

        #endregion

        #region Selection attributes

        public new ISelectionService SelectionContainer
        {
            [DebuggerStepThrough]
            get { return base.SelectionContainer as ISelectionService; }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public DiagramDocView DocView
        {
            [DebuggerStepThrough]
            get
            {
                if (m_docView == null)
                    m_docView = base.SelectionContainer as DiagramDocView;
                return m_docView;
            }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public ModelingDocData DocData
        {
            [DebuggerStepThrough]
            get
            {
                if (m_docData == null && this.DocView != null)
                    m_docData = this.DocView.DocData as ModelingDocData;
                return m_docData;
            }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public Store Store
        {
            [DebuggerStepThrough]
            get
            {
                if (m_store == null && this.DocData != null)
                    m_store = this.DocData.Store;
                return m_store;
            }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public DiagramView DiagramView
        {
            [DebuggerStepThrough]
            get
            {
                if (m_diagramView == null && this.DocView != null)
                    m_diagramView = this.DocView.CurrentDesigner;
                return m_diagramView;
            }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public Diagram Diagram
        {
            [DebuggerStepThrough]
            get
            {
                if (m_diagram == null && this.DocView != null)
                    m_diagram = this.DocView.CurrentDiagram;
                return m_diagram;
            }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public DiagramClientView DiagramClientView
        {
            [DebuggerStepThrough]
            get
            {
                if (m_diagramClientView == null && this.DiagramView != null)
                    m_diagramClientView = this.DiagramView.DiagramClientView;
                return m_diagramClientView;
            }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public SelectedShapesCollection SelectedShapes
        {
            [DebuggerStepThrough]
            get { return this.DiagramView.Selection; }
        }

        #endregion

        #region Standard filters

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public ReadOnlyCollection<ShapeElement> Shapes
        {
            [DebuggerStepThrough]
            get
            {
                List<ShapeElement> result = new List<ShapeElement>(this.Filter<ShapeElement>());
                return new ReadOnlyCollection<ShapeElement>(result);
            }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public ReadOnlyCollection<NodeShape> NodeShapes
        {
            [DebuggerStepThrough]
            get
            {
                List<NodeShape> result = new List<NodeShape>(this.Filter<NodeShape>());
                return new ReadOnlyCollection<NodeShape>(result);
            }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public ReadOnlyCollection<ShapeElement> TopLevelShapes
        {
            [DebuggerStepThrough]
            get
            {
                List<ShapeElement> result = new List<ShapeElement>(
                    this.Filter<ShapeElement>(delegate(ShapeElement shape)
                    {
                        return shape != null && shape.ParentShape is Diagram;
                    }));
                return new ReadOnlyCollection<ShapeElement>(result);
            }
        }

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public ReadOnlyCollection<NodeShape> TopLevelNodeShapes
        {
            [DebuggerStepThrough]
            get
            {
                List<NodeShape> result = new List<NodeShape>(
                    this.Filter<NodeShape>(delegate(NodeShape shape)
                    {
                        return shape != null && shape.ParentShape is Diagram;
                    }));
                return new ReadOnlyCollection<NodeShape>(result);
            }
        }

        #endregion
    }
}
